//
// webserver.c — Minimal multithreaded HTTP static file server (Linux)
// Build:   gcc webserver.c -o webserver -lpthread
// Run:     ./webserver
// Browse:  http://localhost:8080/
//
// Serves files under the WEB_ROOT directory. Defaults to "Index.html".
// Supports keep-alive on GET, basic content-type mapping, and zero-copy sendfile.
//

#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define PORT         8080
#define BUFFER_SIZE  8192
#define WEB_ROOT     "web/"

// -------------------------------
// Request structure & parsing
// -------------------------------
typedef struct {
    char method[16];
    char path[1024];
} Request;

static Request parse_request(const char *buffer) {
    Request req;
    memset(&req, 0, sizeof(req));
    strcpy(req.method, ""); // default empty
    strcpy(req.path, "/");  // default root

    // Very simple parse: METHOD PATH HTTP/1.1
    // e.g., "GET /index.html HTTP/1.1"
    sscanf(buffer, "%15s %1023s", req.method, req.path);
    return req;
}

// -------------------------------
// Utilities
// -------------------------------
static const char *guess_content_type(const char *path) {
    // crude extension check
    const char *ext = strrchr(path, '.');
    if (!ext) return "text/html";
    if (!strcmp(ext, ".html") || !strcmp(ext, ".htm")) return "text/html; charset=utf-8";
    if (!strcmp(ext, ".css"))  return "text/css; charset=utf-8";
    if (!strcmp(ext, ".js"))   return "application/javascript; charset=utf-8";
    if (!strcmp(ext, ".json")) return "application/json; charset=utf-8";
    if (!strcmp(ext, ".png"))  return "image/png";
    if (!strcmp(ext, ".jpg") || !strcmp(ext, ".jpeg")) return "image/jpeg";
    if (!strcmp(ext, ".gif"))  return "image/gif";
    if (!strcmp(ext, ".svg"))  return "image/svg+xml";
    if (!strcmp(ext, ".ico"))  return "image/x-icon";
    if (!strcmp(ext, ".txt"))  return "text/plain; charset=utf-8";
    return "application/octet-stream";
}

static bool contains_dotdot(const char *p) {
    return strstr(p, "..") != NULL;
}

// -------------------------------
// Client handler
// -------------------------------
static void *handle_client(void *arg) {
    int client_fd = *((int *)arg);
    free(arg);

    int keep_alive = 1; // default keep-alive unless "Connection: close"
    char buffer[BUFFER_SIZE];

    while (keep_alive) {
        memset(buffer, 0, sizeof(buffer));
        ssize_t bytes_read = read(client_fd, buffer, sizeof(buffer) - 1);
        if (bytes_read <= 0) break; // closed or error

        // Basic keep-alive check
        if (strstr(buffer, "Connection: close")) {
            keep_alive = 0;
        }

        Request req = parse_request(buffer);

        // Only support GET/HEAD for static content; POST placeholder
        if (!strcmp(req.method, "GET") || !strcmp(req.method, "HEAD")) {
            char full_path[2048];
            struct stat st;

            // Directory traversal guard
            if (contains_dotdot(req.path)) {
                const char *bad = "HTTP/1.1 400 Bad Request\r\n"
                                  "Content-Type: text/plain; charset=utf-8\r\n"
                                  "Connection: close\r\n\r\n"
                                  "Bad Request";
                write(client_fd, bad, strlen(bad));
                break;
            }

            if (!strcmp(req.path, "/")) {
                snprintf(full_path, sizeof(full_path), "%sIndex.html", WEB_ROOT);
            } else {
                // strip leading '/' for local file path
                const char *sub = req.path[0] == '/' ? req.path + 1 : req.path;
                snprintf(full_path, sizeof(full_path), "%s%s", WEB_ROOT, sub);
            }

            if (stat(full_path, &st) < 0 || S_ISDIR(st.st_mode)) {
                const char *not_found =
                    "HTTP/1.1 404 Not Found\r\n"
                    "Content-Type: text/html; charset=utf-8\r\n"
                    "Connection: keep-alive\r\n\r\n"
                    "<!doctype html><html><body><h1>404 Not Found</h1></body></html>";
                write(client_fd, not_found, strlen(not_found));
                continue;
            } else {
                const char *content_type = guess_content_type(full_path);

                // Headers
                char headers[512];
                int n = snprintf(headers, sizeof(headers),
                                 "HTTP/1.1 200 OK\r\n"
                                 "Connection: keep-alive\r\n"
                                 "Content-Type: %s\r\n"
                                 "Content-Length: %ld\r\n\r\n",
                                 content_type, (long)st.st_size);
                write(client_fd, headers, (size_t)n);

                if (!strcmp(req.method, "HEAD")) {
                    continue; // header only
                }

                int file_fd = open(full_path, O_RDONLY);
                if (file_fd != -1) {
                    off_t offset = 0;
                    size_t remaining = (size_t)st.st_size;
                    while (remaining > 0) {
                        ssize_t sent = sendfile(client_fd, file_fd, &offset, remaining);
                        if (sent <= 0) break;
                        remaining -= (size_t)sent;
                    }
                    close(file_fd);
                }
            }
        } else if (!strcmp(req.method, "POST")) {
            // Placeholder: echo 204 No Content (not implemented)
            
            char* contentLengthstr = strstr(buffer,"Content-Length");
            if(!contentLengthstr)
            {
            	printf("No content-length key\n");
    	    }
    	    else
    	    {
            int contentLength;
            sscanf(contentLengthstr+14,":%d",&contentLength);
            char*payload = strstr(buffer, "\r\n\r\n");
            for(int i=0;i<contentLength;i++)
            	printf("%c",*(payload+4+i));
                printf("\n");
            }
            const char *resp =
                "HTTP/1.1 204 No Content\r\n"
                "Connection: keep-alive\r\n\r\n";
            write(client_fd, resp, strlen(resp));
        } else {
            const char *method_not_allowed =
                "HTTP/1.1 405 Method Not Allowed\r\n"
                "Allow: GET, HEAD, POST\r\n"
                "Connection: keep-alive\r\n\r\n";
            write(client_fd, method_not_allowed, strlen(method_not_allowed));
        }

        // Optional: break if the client sent only one request without keep-alive
        // (Most modern browsers default to keep-alive; we keep the loop.)
    }

    close(client_fd);
    return NULL;
}

// -------------------------------
// Main entry
// -------------------------------
int main(void) {
    int server_fd;
    struct sockaddr_in address;
    int opt = 1;

    // Create socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // SO_REUSEADDR
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
        perror("setsockopt");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // Bind
    memset(&address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // Listen
    if (listen(server_fd, 10) < 0) {
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("WebServer running on port %d\n", PORT);
    printf("Access at: http://localhost:%d/\n", PORT);

    // Accept loop
    while (1) {
        int *client_fd = (int *)malloc(sizeof(int));
        if (!client_fd) {
            perror("malloc");
            continue;
        }

        struct sockaddr_in client_addr;
        socklen_t addr_len = sizeof(client_addr);
        *client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
        if (*client_fd < 0) {
            perror("accept");
            free(client_fd);
            continue;
        }

        // Client info (optional)
        char client_ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN);
        // printf("New connection from: %s:%d\n", client_ip, ntohs(client_addr.sin_port));

        pthread_t thread_id;
        if (pthread_create(&thread_id, NULL, handle_client, client_fd) != 0) {
            perror("pthread_create");
            close(*client_fd);
            free(client_fd);
            continue;
        }
        pthread_detach(thread_id); // resources auto-released when thread exits
    }

    // Not reached
    close(server_fd);
    return 0;
}
